feat: add zero-auth install flow with claim command#91
Conversation
Phase 1 core infrastructure for one-shot mode: - One-shot provisioning API client (provisionOneShotEnvironment) - Claim nonce API client (createClaimNonce) - Cookie password generator (generateCookiePassword) - OneShotApiError with status code, timeout, rate limit handling - Config store: add unclaimed environment type and claimToken field - isUnclaimedEnvironment() helper - 30 new tests covering all API scenarios and config round-trips
Add warnIfUnclaimed() module that shows a non-blocking stderr warning when management commands run against an unclaimed environment. Lazily checks claimed status via createClaimNonce() once per session and auto-upgrades config when claimed. Wired into all management command handlers in bin.ts. Updated env list to show (unclaimed) label.
nicknisi
left a comment
There was a problem hiding this comment.
Code Review Findings
Warnings
-
Missing test coverage for unclaimed label display —
env.spec.tslacks tests for the(unclaimed)label inrunEnvList(src/commands/env.ts:221-237). Consider adding tests that verify the label renders correctly for unclaimed environments. -
Missing test for claim command registration —
help-json.spec.tsdoes not explicitly verify theclaimcommand appears in the help registry (src/utils/help-json.ts:1037-1051). A targeted assertion would prevent silent regressions. -
Duplicate
generateCookiePassword— The same function exists in bothsrc/lib/one-shot-api.ts:196andsrc/lib/env-writer.ts:45. Consider extracting to a shared utility module to reduce duplication.
Info
-
False positive in
.case-tested—fail_indicators: 1is a grep heuristic false positive; confirmed 1098/1098 tests passing. -
Pre-existing large files —
run-with-core.ts(541 lines) andhelp-json.ts(1259 lines) were already above the 300-line threshold; changes in this PR are minimal additions.
Automated review by case/reviewer agent
…ork call from warning
- unclaimed-env-api: treat HTTP 409 as alreadyClaimed (not generic error) - claim command: detect 401 in polling loop, clean up and exit - unclaimed-warning: re-add lazy claim check to detect external claims, handle 401 by removing stale claim token
Start a lightweight proxy that injects x-workos-claim-token and x-workos-client-id headers when the active environment is unclaimed. This allows the installer's AI agent to authenticate with the LLM gateway without OAuth tokens.
…in instead of re-provisioning
Subcommands: state (dump raw credentials/config/storage), reset (clear auth state), simulate (write real state for edge-case testing), token (decode JWT claims). Hidden from --help, supports --json output mode.
…ddleware - Extract HOP_BY_HOP_HEADERS, filterHeaders(), buildUpstreamPath() in credential-proxy.ts — eliminates duplicate header/URL logic between startCredentialProxy and startClaimTokenProxy - Replace 82 individual maybeWarnUnclaimed() calls in bin.ts with a single yargs middleware (excludes auth/claim/install/debug/env/skills/doctor) - Add change-detection guard in markEnvironmentClaimed() to skip redundant filesystem writes on already-claimed environments
- saveConfig() now verifies keyring writes with immediate read-back, falling back to file if the keyring entry isn't readable - Provisioning verifies config persisted after save - debug state shows separate Storage sections for credentials and config, each with their own keyring/file diagnostics - markEnvironmentClaimed() renames env key from 'unclaimed' to 'sandbox' - Claim success now prompts user to run `workos auth login`
…imed flow - Split EnvironmentConfig into discriminated union (ClaimedEnvironmentConfig | UnclaimedEnvironmentConfig) with required clientId/claimToken on unclaimed variant, eliminating 4 redundant null checks across the codebase - Make isUnclaimedEnvironment a proper type predicate (env is UnclaimedEnvironmentConfig) - Refactor markEnvironmentClaimed to construct new object instead of mutating + deleting fields - Add logError to outer catch in unclaimed-warning (never-throw preserved) - Surface user-facing message when browser open fails in claim command - Differentiate API vs filesystem errors in tryProvisionUnclaimedEnv - Return false on config read-back failure instead of continuing as success - Add configSource to debug state JSON output for parity with human mode - Use exitWithError in claim command for proper exit code 1 on failures - Remove redundant --json option on claim command (inherited from global)
- Track consecutive poll failures in claim command and update spinner
message after 3+ failures to surface connection issues to user
- Fix credential-proxy module comment ("from file" -> "into upstream
requests") and soften claim token expiry assumption
- Remove vestigial comment in debug.ts that described old approach
- Extract MockUnclaimedEnvApiError to shared test helper to deduplicate
identical classes in claim.spec.ts and unclaimed-warning.spec.ts
- Extract resolveInstallCredentials from bin.ts to standalone module
with 10 unit tests covering the full credential resolution priority
chain (env var > flag > unclaimed > OAuth > provision > login)
- Reorder .env.local write before config save to prevent orphaned config entries on filesystem failure - Add MAX_CONSECUTIVE_FAILURES (10) cap to claim polling loop for early exit instead of burning full 5-minute timeout - Prevent markEnvironmentClaimed from overwriting existing sandbox env - Add logging for non-401 errors in warnIfUnclaimed claim check - Log browser open errors in claim command for diagnostics - Wrap resolveInstallCredentials in try-catch with contextual logging - Replace || with ?? for camelCase/snake_case API field resolution - Add dashboard and default command to middleware exclusion list - Correct RFC citation (2616 → 7230 §6.1) for hop-by-hop headers - Add 9 tests: 401 poll auto-claim, 401 warning promotion, 409 conflict, consecutive failure cap, spinner message, browser open failure, config read-back failure, non-unclaimed no-op, sandbox key collision - Fix exitWithError mock to throw (matches debug.spec.ts pattern) - Add message method to spinner mock
Summary
Implements unclaimed environments — zero-friction
workos installthat works without prior authentication. When no credentials are found, the CLI silently provisions an "unclaimed" environment via the provisioning API, writes all credentials to.env.local, and proceeds with the install. Users can later link the environment to their WorkOS account viaworkos claim. Management commands on unclaimed environments show a non-blocking warning.Also adds a hidden
workos debugcommand for developer-facing CLI introspection (credentials, config, proxy, env vars).Key features
workos installauto-provisions an unclaimed environment when no credentials existworkos claimlinks an unclaimed environment to a WorkOS account via nonce-based browser auth + pollingworkos debugwith subcommands (credentials,config,proxy,env,reset) for developer diagnosticsWhat was tested
Automated
pnpm typecheck— PASSpnpm build— PASSunclaimed-env-api.spec.ts— 20 tests (provisioning, claim initiation, claim polling, error handling)unclaimed-env-provision.spec.ts— 10 tests (happy path, API failure fallback, config store writes)claim.spec.ts— 12 tests (nonce generation, browser open, polling, timeout, JSON mode)unclaimed-warning.spec.ts— 9 tests (warning display, dedup, JSON suppression, claimed detection)debug.spec.ts— 31 tests (all subcommands, JSON mode, masking, error handling)config-store.spec.ts— +11 tests (unclaimed type, claimToken field, type guard)Manual
Full code review of all 25 changed files. Each acceptance criterion verified against source code and tests:
(unclaimed)label with hint to runworkos claimmaybeWarnUnclaimed()across command handlersKey design decisions
tryUnclaimedProvision()catches all errors and returnsfalse, allowing seamless fallback to the existingensureAuthenticated()login flowmaybeWarnUnclaimed()uses a module-level flag to prevent repeated warnings across multiple commands in a sessionfalsedescription in yargs so it doesn't appear in--helpoutput — developer-only toolFiles changed (25)
src/lib/unclaimed-env-api.tssrc/lib/unclaimed-env-api.spec.tssrc/lib/unclaimed-env-provision.tstryUnclaimedProvision()helpersrc/lib/unclaimed-env-provision.spec.tssrc/lib/unclaimed-warning.tssrc/lib/unclaimed-warning.spec.tssrc/commands/claim.tsworkos claimcommandsrc/commands/claim.spec.tssrc/commands/debug.tsworkos debugintrospection commandsrc/commands/debug.spec.tssrc/utils/box.tssrc/lib/config-store.tsunclaimedtype,claimTokenfieldsrc/lib/config-store.spec.tssrc/lib/credential-proxy.tssrc/lib/agent-interface.tssrc/commands/env.tssrc/lib/env-writer.tssrc/lib/run-with-core.tsmaybeWarnUnclaimed()wiringsrc/bin.tssrc/utils/help-json.tsREADME.md.gitignorepackage.json.release-please-manifest.jsonCHANGELOG.mdFollow-ups
env.spec.tslacks tests for unclaimed label display inrunEnvListhelp-json.spec.tsdoes not explicitly verify claim command registrationgenerateCookiePasswordexists inunclaimed-env-api.tsandenv-writer.ts— consider extracting to a shared utilityScreenshots
Unclaimed environment created

Warning about taking actions on an unclaimed environment

Claim an environment
